package client;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.DateFormat;

import org.apache.commons.codec.binary.Base64;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;

import play.Logger;
import play.Play;

import com.ineatconseil.yougo.dto.DetailedUserDto;
import com.ineatconseil.yougo.dto.RequestDto;
import com.ineatconseil.yougo.dto.RequestTypeDto;
import com.ineatconseil.yougo.dto.UserDto;
import com.ineatconseil.yougo.dto.UserTypeDto;

public class YouGoClient implements YouGo {

	public static final DateFormat DF = new Iso8601DateFormat();
	private String baseURL;
	private String auth;
	private final static ObjectMapper mapper = new ObjectMapper();
	{
		mapper.getDeserializationConfig().setDateFormat(DF);
		mapper.getSerializationConfig().setDateFormat(DF);
		mapper.getSerializationConfig().set(Feature.WRITE_DATES_AS_TIMESTAMPS, false);

	}
	private static final String CHARSET = "UTF-8";
	Proxy proxy;

	public YouGoClient() {
		baseURL = Play.configuration.getProperty("yougo.baseurl");
		auth = Play.configuration.getProperty("yougo.auth");
	}

	public UserDto[] getUsers() {
		try {
			URLConnection connection = getConnection("/users");
			connection.connect();
			UserDto[] users = (UserDto[]) mapper.readValue(connection.getInputStream(), UserDto[].class);
			return users;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private Object requestGet(String path, Class valueType) throws RestException {
		try {
			HttpURLConnection connection = getConnection(path);
			connection.setRequestMethod("GET");
			connection.connect();
			Object readValue = mapper.readValue(connection.getInputStream(), valueType);
			Logger.info("GET '%s' [%s - %s]", path, connection.getResponseCode(), connection.getResponseMessage());
			connection.disconnect();

			if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
				throw new RestException();
			}

			return readValue;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private void requestPut(String path, Object o) throws RestException {
		try {
			HttpURLConnection connection = getConnection(path);
			connection.setRequestMethod("PUT");
			connection.setUseCaches(true);
			connection.setDoInput(true);
			connection.setDoOutput(true);
			connection.connect();
			mapper.writeValue(connection.getOutputStream(), o);
			Logger.info("PUT '%s' [%s - %s]", path, connection.getResponseCode(), connection.getResponseMessage());
			connection.disconnect();
			if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
				throw new RestException();
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private void requestPost(String path, Object o) throws RestException {
		try {
			HttpURLConnection connection = getConnection(path);
			connection.setRequestMethod("POST");
			connection.setUseCaches(true);
			connection.setDoInput(true);
			connection.setDoOutput(true);
			connection.connect();
			mapper.writeValue(connection.getOutputStream(), o);

			String filename = path.replace("/", ".");
			FileWriter fstream = new FileWriter(filename + ".txt");
			BufferedWriter out = new BufferedWriter(fstream);
			mapper.writeValue(out, o);
			out.close();

			Logger.info("POST '%s' [%s - %s]", path, connection.getResponseCode(), connection.getResponseMessage());
			connection.disconnect();
			if (connection.getResponseCode() != HttpURLConnection.HTTP_CREATED) {
				throw new RestException();
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void requestDelete(String path) throws RestException {
		try {
			HttpURLConnection connection = getConnection(path);
			connection.setRequestMethod("DELETE");
			connection.connect();
			Logger.info("DELETE '%s' [%s - %s]", path, connection.getResponseCode(), connection.getResponseMessage());
			connection.disconnect();
			if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
				throw new RestException();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public HttpURLConnection getConnection(String path) {
		try {
			java.net.URL url = new java.net.URL(baseURL + path);
			java.net.URLConnection connection = (proxy != null ? url.openConnection(proxy) : url.openConnection());

			byte[] authEncBytes = Base64.encodeBase64(auth.getBytes());
			String authStringEnc = new String(authEncBytes);
			connection.setRequestProperty("Authorization", "Basic " + authStringEnc);
			connection.setRequestProperty("Content-Type", "application/json");
			connection.setRequestProperty("Accept-Charset", CHARSET);

			return (HttpURLConnection) connection;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public DetailedUserDto[] getDetailedUsers() throws RestException {
		return (DetailedUserDto[]) requestGet("/detailed-users", DetailedUserDto[].class);
	}

	public RequestTypeDto[] getRequestTypeDto() throws RestException {
		return (RequestTypeDto[]) requestGet("/request-types", RequestTypeDto[].class);
	}

	public void postRequestTypeDto(RequestTypeDto req) throws RestException {
		requestPost("/request-types", req);
	}

	public RequestTypeDto getRequestTypeDto(Long id) throws RestException {
		return (RequestTypeDto) requestGet("/request-types/" + id, RequestTypeDto.class);
	}

	public void deleteRequestTypeDto(Long id) throws RestException {
		requestDelete("/request-types/" + id);
	}

	public UserDto[] getUserDtos() throws RestException {
		return (UserDto[]) requestGet("/users", UserDto[].class);
	}

	public UserDto getUserDto(String email) throws RestException {
		return (UserDto) requestGet("/users/" + email, UserDto.class);
	}

	public UserDto getUserDtoById(Long id) throws RestException {
		return (UserDto) requestGet("/users/" + id, UserDto.class);
	}

	public void deleteUserDto(Long id) throws RestException {
		requestDelete("/users/" + id);
	}

	public void postUserDtos(UserDto user) throws RestException {
		requestPost("/users", user);
	}

	public void putUserDto(UserDto user) throws RestException {
		requestPut("/users/" + user.getId(), user);
	}

	public void putUserDtoPassword(UserDto user, String oldPsw, String psw, String confirmedPsw) throws RestException {
		try {
			String query =
					String.format("oldPassword=%s&newPassword=%s&confirmedNewPassword=%s",
							URLEncoder.encode(oldPsw, CHARSET), URLEncoder.encode(psw, CHARSET),
							URLEncoder.encode(confirmedPsw, CHARSET));
			StringBuilder sb = new StringBuilder("/users/");
			sb.append(user.getId()).append("/password");
			//sb.append(query);
			
			PasswordDto p = new PasswordDto(oldPsw,psw,confirmedPsw);
			requestPut(sb.toString(), p);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

	}

	public RequestDto[] getRequestDtos(Long userid) throws RestException {
		return (RequestDto[]) requestGet("/users/" + userid + "/requests", RequestDto[].class);
	}

	public RequestDto getRequestDtos(Long userid, Long id) throws RestException {
		return (RequestDto) requestGet("/users/" + userid + "/requests/" + id, RequestDto.class);
	}

	public void deleteRequestDtos(Long userid, Long id) throws RestException {
		requestDelete("/users/" + userid + "/requests/" + id);
	}

	public UserTypeDto[] getUserTypeDto() throws RestException {
		return (UserTypeDto[]) requestGet("/user-types", UserTypeDto[].class);
	}

	public UserTypeDto getUserTypeDto(Long id) throws RestException {
		return (UserTypeDto) requestGet("/user-types/" + id, UserTypeDto.class);
	}

	public void deleteUserTypeDto(Long id) throws RestException {
		requestDelete("/user-types/" + id);
	}

	public void postRequestDtos(Long userid, RequestDto req) throws RestException {
		requestPost("/users/" + userid + "/requests", req);
	}

	public void postUserTypeDto(UserTypeDto user) throws RestException {
		requestPost("/user-types", user);

	}

	public void putTypeDto(RequestTypeDto req) throws RestException {
		requestPut("/user-types", req);
	}

	public void putRequestDtos(Long userid, RequestDto req) throws RestException {
		requestPut("/users/" + userid + "/requests/" + req.getId(), req);
	}

	public void putUserTypeDto(UserTypeDto userType) throws RestException {
		requestPut("/user-types/" + userType.getId(), userType);
	}

}
